1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.apache.tapestry5.internal.plastic;
16
17 import org.apache.tapestry5.internal.plastic.asm.ClassReader;
18 import org.apache.tapestry5.internal.plastic.asm.ClassWriter;
19 import org.apache.tapestry5.internal.plastic.asm.Opcodes;
20 import org.apache.tapestry5.internal.plastic.asm.tree.*;
21 import org.apache.tapestry5.plastic.*;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 import java.io.BufferedInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.lang.annotation.Annotation;
29 import java.lang.reflect.Modifier;
30 import java.util.*;
31 import java.util.concurrent.CopyOnWriteArrayList;
32
33
34
35
36
37 @SuppressWarnings("rawtypes")
38 public class PlasticClassPool implements ClassLoaderDelegate, Opcodes, PlasticClassListenerHub
39 {
40 private static final Logger LOGGER = LoggerFactory.getLogger(PlasticClassPool.class);
41
42 final PlasticClassLoader loader;
43
44 private final PlasticManagerDelegate delegate;
45
46 private final Set<String> controlledPackages;
47
48 private final Map<String, Boolean> checkedExceptionCache = new HashMap<String, Boolean>();
49
50
51
52
53 private final Stack<String> activeInstrumentClassNames = new Stack<String>();
54
55
56
57
58
59 private final Map<String, ClassInstantiator> instantiators = PlasticInternalUtils.newMap();
60
61 private final InheritanceData emptyInheritanceData = new InheritanceData(null);
62
63 private final StaticContext emptyStaticContext = new StaticContext();
64
65 private final List<PlasticClassListener> listeners = new CopyOnWriteArrayList<PlasticClassListener>();
66
67 private final Cache<String, TypeCategory> typeName2Category = new Cache<String, TypeCategory>()
68 {
69 @Override
70 protected TypeCategory convert(String typeName)
71 {
72 ClassNode cn = constructClassNodeFromBytecode(typeName);
73
74 return Modifier.isInterface(cn.access) ? TypeCategory.INTERFACE : TypeCategory.CLASS;
75 }
76 };
77
78 static class BaseClassDef
79 {
80 final InheritanceData inheritanceData;
81
82 final StaticContext staticContext;
83
84 public BaseClassDef(InheritanceData inheritanceData, StaticContext staticContext)
85 {
86 this.inheritanceData = inheritanceData;
87 this.staticContext = staticContext;
88 }
89 }
90
91
92
93
94 private final Map<String, BaseClassDef> baseClassDefs = PlasticInternalUtils.newMap();
95
96
97 private final Map<String, FieldInstrumentations> instrumentations = PlasticInternalUtils.newMap();
98
99 private final Map<String, String> transformedClassNameToImplementationClassName = PlasticInternalUtils.newMap();
100
101
102 private final FieldInstrumentations placeholder = new FieldInstrumentations(null);
103
104
105 private final Set<TransformationOption> options;
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 public PlasticClassPool(ClassLoader parentLoader, PlasticManagerDelegate delegate, Set<String> controlledPackages,
121 Set<TransformationOption> options)
122 {
123 loader = new PlasticClassLoader(parentLoader, this);
124 this.delegate = delegate;
125 this.controlledPackages = controlledPackages;
126 this.options = options;
127 }
128
129 public ClassLoader getClassLoader()
130 {
131 return loader;
132 }
133
134 public Class realizeTransformedClass(ClassNode classNode, InheritanceData inheritanceData,
135 StaticContext staticContext)
136 {
137 synchronized (loader)
138 {
139 Class result = realize(PlasticInternalUtils.toClassName(classNode.name), ClassType.PRIMARY, classNode);
140 baseClassDefs.put(result.getName(), new BaseClassDef(inheritanceData, staticContext));
141
142 return result;
143 }
144
145 }
146
147 public Class realize(String primaryClassName, ClassType classType, ClassNode classNode)
148 {
149 synchronized (loader)
150 {
151 if (!listeners.isEmpty())
152 {
153 fire(toEvent(primaryClassName, classType, classNode));
154 }
155
156 byte[] bytecode = toBytecode(classNode);
157
158 String className = PlasticInternalUtils.toClassName(classNode.name);
159
160 return loader.defineClassWithBytecode(className, bytecode);
161 }
162 }
163
164 private PlasticClassEvent toEvent(final String primaryClassName, final ClassType classType,
165 final ClassNode classNode)
166 {
167 return new PlasticClassEvent()
168 {
169 @Override
170 public ClassType getType()
171 {
172 return classType;
173 }
174
175 @Override
176 public String getPrimaryClassName()
177 {
178 return primaryClassName;
179 }
180
181 @Override
182 public String getDissasembledBytecode()
183 {
184 return PlasticInternalUtils.dissasembleBytecode(classNode);
185 }
186
187 @Override
188 public String getClassName()
189 {
190 return PlasticInternalUtils.toClassName(classNode.name);
191 }
192 };
193 }
194
195 private void fire(PlasticClassEvent event)
196 {
197 for (PlasticClassListener listener : listeners)
198 {
199 listener.classWillLoad(event);
200 }
201 }
202
203 private byte[] toBytecode(ClassNode classNode)
204 {
205 ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
206
207 classNode.accept(writer);
208
209 return writer.toByteArray();
210 }
211
212 public AnnotationAccess createAnnotationAccess(String className)
213 {
214 try
215 {
216 final Class<?> searchClass = loader.loadClass(className);
217
218 return new AnnotationAccess()
219 {
220 @Override
221 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
222 {
223 return getAnnotation(annotationType) != null;
224 }
225
226 @Override
227 public <T extends Annotation> T getAnnotation(Class<T> annotationType)
228 {
229 return searchClass.getAnnotation(annotationType);
230 }
231 };
232 } catch (Exception ex)
233 {
234 throw new RuntimeException(ex);
235 }
236 }
237
238 public AnnotationAccess createAnnotationAccess(List<AnnotationNode> annotationNodes)
239 {
240 if (annotationNodes == null)
241 {
242 return EmptyAnnotationAccess.SINGLETON;
243 }
244
245 final Map<String, Object> cache = PlasticInternalUtils.newMap();
246 final Map<String, AnnotationNode> nameToNode = PlasticInternalUtils.newMap();
247
248 for (AnnotationNode node : annotationNodes)
249 {
250 nameToNode.put(PlasticInternalUtils.objectDescriptorToClassName(node.desc), node);
251 }
252
253 return new AnnotationAccess()
254 {
255 @Override
256 public <T extends Annotation> boolean hasAnnotation(Class<T> annotationType)
257 {
258 return nameToNode.containsKey(annotationType.getName());
259 }
260
261 @Override
262 public <T extends Annotation> T getAnnotation(Class<T> annotationType)
263 {
264 String className = annotationType.getName();
265
266 Object result = cache.get(className);
267
268 if (result == null)
269 {
270 result = buildAnnotation(className);
271
272 if (result != null)
273 cache.put(className, result);
274 }
275
276 return annotationType.cast(result);
277 }
278
279 private Object buildAnnotation(String className)
280 {
281 AnnotationNode node = nameToNode.get(className);
282
283 if (node == null)
284 return null;
285
286 return createAnnotation(className, node);
287 }
288 };
289 }
290
291 Class loadClass(String className)
292 {
293 try
294 {
295 return loader.loadClass(className);
296 } catch (Exception ex)
297 {
298 throw new RuntimeException(String.format("Unable to load class %s: %s", className,
299 PlasticInternalUtils.toMessage(ex)), ex);
300 }
301 }
302
303 protected Object createAnnotation(String className, AnnotationNode node)
304 {
305 AnnotationBuilder builder = new AnnotationBuilder(loadClass(className), this);
306
307 node.accept(builder);
308
309 return builder.createAnnotation();
310 }
311
312 @Override
313 public boolean shouldInterceptClassLoading(String className)
314 {
315 int searchFromIndex = className.length() - 1;
316
317 while (true)
318 {
319 int dotx = className.lastIndexOf('.', searchFromIndex);
320
321 if (dotx < 0)
322 break;
323
324 String packageName = className.substring(0, dotx);
325
326 if (controlledPackages.contains(packageName))
327 return true;
328
329 searchFromIndex = dotx - 1;
330 }
331
332 return false;
333 }
334
335
336
337 @Override
338 public synchronized Class<?> loadAndTransformClass(String className) throws ClassNotFoundException
339 {
340
341
342 if (className.contains("$"))
343 {
344 return loadInnerClass(className);
345 }
346
347
348
349
350 if (activeInstrumentClassNames.contains(className))
351 {
352 StringBuilder builder = new StringBuilder("");
353 String sep = "";
354
355 for (String name : activeInstrumentClassNames)
356 {
357 builder.append(sep);
358 builder.append(name);
359
360 sep = ", ";
361 }
362
363 throw new IllegalStateException(String.format("Unable to transform class %s as it is already in the process of being transformed; there is a cycle among the following classes: %s.",
364 className, builder));
365 }
366
367 activeInstrumentClassNames.push(className);
368
369 try
370 {
371
372 InternalPlasticClassTransformation transformation = getPlasticClassTransformation(className);
373
374 delegate.transform(transformation.getPlasticClass());
375
376 ClassInstantiator createInstantiator = transformation.createInstantiator();
377 ClassInstantiator configuredInstantiator = delegate.configureInstantiator(className, createInstantiator);
378
379 instantiators.put(className, configuredInstantiator);
380
381 return transformation.getTransformedClass();
382 } finally
383 {
384 activeInstrumentClassNames.pop();
385 }
386 }
387
388 private Class loadInnerClass(String className)
389 {
390 ClassNode classNode = constructClassNodeFromBytecode(className);
391
392 interceptFieldAccess(classNode);
393
394 return realize(className, ClassType.INNER, classNode);
395 }
396
397 private void interceptFieldAccess(ClassNode classNode)
398 {
399 for (MethodNode method : classNode.methods)
400 {
401 interceptFieldAccess(classNode.name, method);
402 }
403 }
404
405 private void interceptFieldAccess(String classInternalName, MethodNode method)
406 {
407 InsnList insns = method.instructions;
408
409 ListIterator it = insns.iterator();
410
411 while (it.hasNext())
412 {
413 AbstractInsnNode node = (AbstractInsnNode) it.next();
414
415 int opcode = node.getOpcode();
416
417 if (opcode != GETFIELD && opcode != PUTFIELD)
418 {
419 continue;
420 }
421
422 FieldInsnNode fnode = (FieldInsnNode) node;
423
424 String ownerInternalName = fnode.owner;
425
426 if (ownerInternalName.equals(classInternalName))
427 {
428 continue;
429 }
430
431 FieldInstrumentation instrumentation = getFieldInstrumentation(ownerInternalName, fnode.name, opcode == GETFIELD);
432
433 if (instrumentation == null)
434 {
435 continue;
436 }
437
438
439
440 insns.insertBefore(fnode, new MethodInsnNode(INVOKEVIRTUAL, ownerInternalName, instrumentation.methodName, instrumentation.methodDescription));
441
442 it.remove();
443 }
444 }
445
446
447
448
449
450
451
452
453 public InternalPlasticClassTransformation getPlasticClassTransformation(String className)
454 throws ClassNotFoundException
455 {
456 assert PlasticInternalUtils.isNonBlank(className);
457
458 ClassNode classNode = constructClassNodeFromBytecode(className);
459
460 String baseClassName = PlasticInternalUtils.toClassName(classNode.superName);
461
462 instrumentations.put(classNode.name, new FieldInstrumentations(classNode.superName));
463
464
465 return createTransformation(baseClassName, classNode, null, false);
466 }
467
468
469
470
471
472
473
474
475
476
477
478
479 private InternalPlasticClassTransformation createTransformation(String baseClassName, ClassNode classNode, ClassNode implementationClassNode, boolean proxy)
480 throws ClassNotFoundException
481 {
482 if (shouldInterceptClassLoading(baseClassName))
483 {
484 loader.loadClass(baseClassName);
485
486 BaseClassDef def = baseClassDefs.get(baseClassName);
487
488 assert def != null;
489
490 return new PlasticClassImpl(classNode, implementationClassNode, this, def.inheritanceData, def.staticContext, proxy);
491 }
492
493
494
495 return new PlasticClassImpl(classNode, implementationClassNode, this, emptyInheritanceData, emptyStaticContext, proxy);
496 }
497
498
499
500
501
502
503
504
505
506 public ClassNode constructClassNodeFromBytecode(String className)
507 {
508 byte[] bytecode = readBytecode(className);
509
510 if (bytecode == null)
511 return null;
512
513 return PlasticInternalUtils.convertBytecodeToClassNode(bytecode);
514 }
515
516 private byte[] readBytecode(String className)
517 {
518 ClassLoader parentClassLoader = loader.getParent();
519
520 return PlasticInternalUtils.readBytecodeForClass(parentClassLoader, className, true);
521 }
522
523 public PlasticClassTransformation createTransformation(String baseClassName, String newClassName)
524 {
525 return createTransformation(baseClassName, newClassName, null);
526 }
527
528 public PlasticClassTransformation createTransformation(String baseClassName, String newClassName, String implementationClassName)
529 {
530 try
531 {
532 ClassNode newClassNode = new ClassNode();
533
534 final String internalNewClassNameinternalName = PlasticInternalUtils.toInternalName(newClassName);
535 final String internalBaseClassName = PlasticInternalUtils.toInternalName(baseClassName);
536 newClassNode.visit(PlasticConstants.DEFAULT_VERSION_OPCODE, ACC_PUBLIC, internalNewClassNameinternalName, null, internalBaseClassName, null);
537
538 ClassNode implementationClassNode = null;
539
540 if (implementationClassName != null)
541 {
542
543
544
545
546
547 if (transformedClassNameToImplementationClassName.containsKey(implementationClassName))
548 {
549 implementationClassName =
550 transformedClassNameToImplementationClassName.get(implementationClassName);
551 }
552
553 if (!implementationClassName.startsWith("com.sun.proxy"))
554 {
555
556 try
557 {
558 implementationClassNode = readClassNode(implementationClassName);
559 } catch (IOException e)
560 {
561 LOGGER.warn(String.format("Unable to load class %s as the implementation of service %s",
562 implementationClassName, baseClassName));
563
564 }
565
566 }
567
568 transformedClassNameToImplementationClassName.put(newClassName, implementationClassName);
569
570 }
571
572 return createTransformation(baseClassName, newClassNode, implementationClassNode, true);
573 } catch (ClassNotFoundException ex)
574 {
575 throw new RuntimeException(String.format("Unable to create class %s as sub-class of %s: %s", newClassName,
576 baseClassName, PlasticInternalUtils.toMessage(ex)), ex);
577 }
578
579 }
580
581 private ClassNode readClassNode(String className) throws IOException
582 {
583 return readClassNode(className, getClassLoader());
584 }
585
586 static ClassNode readClassNode(String className, ClassLoader classLoader) throws IOException
587 {
588 ClassNode classNode = new ClassNode();
589 final String location = PlasticInternalUtils.toInternalName(className) + ".class";
590 InputStream inputStream = classLoader.getResourceAsStream(location);
591 BufferedInputStream bis = new BufferedInputStream(inputStream);
592 ClassReader classReader = new ClassReader(inputStream);
593 inputStream.close();
594 bis.close();
595 classReader.accept(classNode, 0);
596 return classNode;
597
598 }
599
600 public ClassInstantiator getClassInstantiator(String className)
601 {
602 synchronized (loader)
603 {
604 if (!instantiators.containsKey(className))
605 {
606 try
607 {
608 loader.loadClass(className);
609 } catch (ClassNotFoundException ex)
610 {
611 throw new RuntimeException(ex);
612 }
613 }
614
615 ClassInstantiator result = instantiators.get(className);
616
617 if (result == null)
618 {
619
620
621 StringBuilder b = new StringBuilder();
622 b.append("Class '")
623 .append(className)
624 .append("' is not a transformed class. Transformed classes should be in one of the following packages: ");
625
626 String sep = "";
627
628 List<String> names = new ArrayList<String>(controlledPackages);
629 Collections.sort(names);
630
631 for (String name : names)
632 {
633 b.append(sep);
634 b.append(name);
635
636 sep = ", ";
637 }
638
639 String message = b.append('.').toString();
640
641 throw new IllegalArgumentException(message);
642 }
643
644 return result;
645 }
646 }
647
648 TypeCategory getTypeCategory(String typeName)
649 {
650 synchronized (loader)
651 {
652
653
654 return typeName2Category.get(typeName);
655 }
656 }
657
658 @Override
659 public void addPlasticClassListener(PlasticClassListener listener)
660 {
661 assert listener != null;
662
663 listeners.add(listener);
664 }
665
666 @Override
667 public void removePlasticClassListener(PlasticClassListener listener)
668 {
669 assert listener != null;
670
671 listeners.remove(listener);
672 }
673
674 boolean isEnabled(TransformationOption option)
675 {
676 return options.contains(option);
677 }
678
679
680 void setFieldReadInstrumentation(String classInternalName, String fieldName, FieldInstrumentation fi)
681 {
682 instrumentations.get(classInternalName).read.put(fieldName, fi);
683 }
684
685
686 private FieldInstrumentations getFieldInstrumentations(String classInternalName)
687 {
688 FieldInstrumentations result = instrumentations.get(classInternalName);
689
690 if (result != null)
691 {
692 return result;
693 }
694
695 String className = PlasticInternalUtils.toClassName(classInternalName);
696
697
698
699
700 if (!className.contains("$") && shouldInterceptClassLoading(className))
701 {
702 try
703 {
704 loadAndTransformClass(className);
705
706
707
708 return instrumentations.get(classInternalName);
709 } catch (Exception ex)
710 {
711 throw new RuntimeException(PlasticInternalUtils.toMessage(ex), ex);
712 }
713 }
714
715
716
717
718 result = placeholder;
719 instrumentations.put(classInternalName, result);
720
721 return result;
722 }
723
724 FieldInstrumentation getFieldInstrumentation(String ownerClassInternalName, String fieldName, boolean forRead)
725 {
726 String currentName = ownerClassInternalName;
727
728 while (true)
729 {
730
731 if (currentName == null)
732 {
733 return null;
734 }
735
736 FieldInstrumentations instrumentations = getFieldInstrumentations(currentName);
737
738 FieldInstrumentation instrumentation = instrumentations.get(fieldName, forRead);
739
740 if (instrumentation != null)
741 {
742 return instrumentation;
743 }
744
745 currentName = instrumentations.superClassInternalName;
746 }
747 }
748
749
750 void setFieldWriteInstrumentation(String classInternalName, String fieldName, FieldInstrumentation fi)
751 {
752 instrumentations.get(classInternalName).write.put(fieldName, fi);
753 }
754
755 boolean isCheckedException(String exceptionName)
756 {
757 Boolean cached = checkedExceptionCache.get(exceptionName);
758
759 if (cached != null)
760 {
761 return cached;
762 }
763
764 try
765 {
766 Class asClass = getClassLoader().loadClass(exceptionName);
767
768
769 boolean checked = !(Error.class.isAssignableFrom(asClass) ||
770 RuntimeException.class.isAssignableFrom(asClass));
771
772 checkedExceptionCache.put(exceptionName, checked);
773
774 return checked;
775 } catch (Exception e)
776 {
777 throw new RuntimeException(e);
778 }
779 }
780 }
781
782